從需求到實作,前面走過的都是還沒真正交出去之前的事。今天要講的是最後一段,東西寫好了,也核對過沒跑偏,接下來要真的把它推到會有真人使用的地方去。這段路走得再穩,只要上線這一步做得馬虎,前面所有的把關都可能白費,因為使用者看到的,是上線之後那個版本,不是開發過程中任何一個版本。
這種前功盡棄的可能性,讓上線這段特別值得放慢腳步、認真對待,不能因為前面已經很累了,就想快點結束這整件事。前面每一段其實都在為這一刻默默做準備,需求講清楚是為了先知道要做對什麼,設計架構收斂是為了知道具體要怎麼做,實作核對是為了確保真的做對了,這些準備全部指向同一個終點,就是讓上線這一刻,交出去的東西真的是原本想要的那個樣子,不多也不少。如果這最後一步鬆懈了,前面每一段花下去的用心,等於都失去了原本要達成的那個意義,變成一場沒有終點的白忙一場。
開發的時候用的資料、用的設定,很多時候是刻意準備好、乾淨又有限的,跟真實環境會遇到的情況差很多。真實環境的資料量可能大上好幾個量級,權限設定可能更嚴格,網路狀況也不會永遠穩定。這些差異,在開發階段的核對裡完全測不出來,因為開發階段用的就是那個乾淨的假設。
這種落差最麻煩的地方,是它常常不會在功能邏輯上出問題,而是在效能或穩定性上出問題,邏輯對不對,開發階段就能核對出來,資料量變大之後會不會變慢、會不會超出某個限制,這種問題只有在接近真實規模的情況下才會浮現,開發階段的核對再仔細,也碰不到這一塊,因為當時根本沒有那個規模的資料可以測。
我自己在真正推上去之前,會特別把這些跟真實環境有關的差異拉出來,一條一條想過,這個功能如果資料量變大十倍會不會變慢,這個設定如果換成正式環境的權限會不會被擋下來,這種思考完全沒辦法靠開發階段的核對自動涵蓋進去,得自己刻意花時間想過一次才行。這件事我自己一直覺得特別容易被忽略掉,因為前面每一關都已經做得很仔細,很容易產生一種「應該都測過了」的錯覺,卻完全沒意識到,測試時用的環境,跟真正要跑起來的環境,根本就不是同一回事。
這份差異清單我不會每次都從零想起,會把每次真的遇到過的環境落差記下來,累積成一份可以重複查的對照表。下次要上線新東西,先翻這份表,看看有哪些過去踩過的落差這次也可能出現,這樣每次要想的範圍會越來越集中在真正還沒遇過的新情況,而不是每次都重新發明一遍已經知道的教訓。
這份清單累積久了之後,也慢慢變成一種對這個系統特性的整體理解,哪些地方特別敏感、哪些假設在這個系統裡特別容易不成立,這些知識完全沒有寫在任何規格文件裡,是純粹從一次次真實上線經驗裡慢慢累積出來的,某種程度上,這份清單本身就是這個系統獨有的一部分知識,換一個系統不一定完全適用,但累積這份清單的習慣本身,是可以直接帶到下一個系統繼續用的,這才是真正值得保留下來的東西。
推上線這個動作本身,如果每次都靠人手動一步一步操作,出錯的機率會隨著次數增加。人在重複做一件事的時候,特別容易在某個熟悉到不會多想的步驟上疏忽,漏掉一步,或者順序搞錯,這種錯誤往往不是能力問題,是重複性動作本身就容易讓人放鬆警覺。
越是熟練的動作,越容易在腦子放空的狀態下完成,這種放空狀態平常沒什麼問題,遇到今天流程剛好跟平常不太一樣的時候就會出事,因為肌肉記憶帶著人照著舊有的順序走,完全沒注意到今天其實需要多一個步驟或者少一個步驟。這種因為熟練而產生的疏忽,越有經驗的人反而越容易中招,因為熟練帶來的自動化程度更高,也就更難察覺今天的情況跟平常不一樣,經驗在這裡反而變成一種盲點,而不是保護傘。
我自己現在的做法是,把部署這個動作寫成一套可以重複執行、每次都照同樣順序跑的流程,不靠當下的記憶去決定這次要做哪些步驟。這樣做的好處是,不管是誰在什麼狀態下執行這個流程,跑出來的結果都一樣,不會因為那天比較累、比較趕,就漏掉某個平常不會漏的步驟。這件事本質上跟前面幾天講的設計系統規則、驗收條件核對是同一個道理,把容易因為人為狀態而波動的東西,變成一套穩定的固定流程,才靠得住。
這套流程寫好之後,我自己反而比較敢在比較累、比較趕的時候推動上線這件事,因為知道流程本身不會因為我當下的狀態打折扣,該做的檢查跟步驟都寫死在裡面,不會因為我那天想快點結束就跳過。這跟完全靠自己當下的專注力去確保每一步都做對,是兩種完全不同等級的可靠性,前者靠的是流程本身的穩定,後者靠的是人的意志力,意志力這種東西太不穩定,不該是保護正式環境的最後一道防線。
東西推上去之後,很多人會有一種鬆一口氣、任務完成的感覺,但實際上,上線之後才是這個東西第一次真正接受檢驗的時刻。開發跟核對階段驗證的是符不符合當初的規劃,上線之後驗證的是真實使用情況下究竟會不會出事,這是兩種性質完全不同的檢驗,前者過了,完全不代表後者也一定沒有問題。
這種鬆一口氣的心理其實完全可以理解,前面幾段確實花了不少力氣,逼問、盤點、組隊分析、逐條核對,每一段都不輕鬆,走到上線這一步,很自然就會覺得,辛苦的部分應該都已經過去了。但真正對使用者有意義的檢驗,恰好正是從這一刻才真正開始,之前的每一段努力,都只是為了讓這個東西有資格接受這場真正的檢驗而已,並不是檢驗本身。把上線當成一整件事的結束,等於在真正的考試前一天,就急著宣布自己已經考完了,還考了個滿分。
我自己現在會在上線之後,特別留意有沒有辦法知道這個東西目前的狀態,是正常運作,還是已經出了問題但沒有人發現。沒有這種觀察的手段,等於是把東西丟出去之後就完全矇著眼睛,只能等到有人主動反映問題才知道出事了,那個時候問題可能已經影響了不少使用情況,比早一步自己發現代價高得多。
這種矇著眼睛的狀態,最危險的地方在於它完全不會讓人感覺到任何不安,反而因為沒有壞消息傳來,會誤以為一切都很順利,這種假性的安心感,比明確知道有問題還要糟糕,因為明確知道有問題,至少會促使人採取行動去處理,假性的安心只會讓問題在看不見的地方持續累積,等到終於被發現的那一刻,往往已經演變成比較嚴重的狀態了。
很多人會誤以為東西上線之後,只要沒有收到任何回報,就代表一切都很正常,這個假設其實非常危險,因為使用者不一定會主動回報問題,很多時候是默默放棄使用,或者根本沒意識到自己當下遇到的情況其實是個問題。沒消息這件事,更接近的解讀是還沒有人主動告訴你出事了,並不代表真的什麼事都沒有發生。
那種會默默放棄使用的人,通常也正是最不會留下任何回報的人,這代表最容易被忽略的問題,反而可能是影響範圍最大的那種,因為它讓最多人選擇直接離開,而不是留下抱怨,離開的人是不會出現在任何回報清單上的。單純靠回報數量去判斷問題嚴不嚴重,很容易錯估情況,回報少不代表問題少,可能只是代表大家選擇用腳投票,不是選擇開口,而用腳投票這件事,本身就不會留下任何看得見的痕跡。
我自己的做法是,上線之後會主動去確認幾個關鍵的指標,這個功能實際被使用的狀況正不正常,有沒有出現不該出現的錯誤,而不是被動等著別人來通知。這種主動確認的習慣,跟前面一直強調的道理相通,不能因為沒人反應問題,就自己說服自己這件事沒問題,沒有主動驗證過的假設,跟猜測沒有兩樣。
哪些指標值得看,我會回到當初規劃階段定下來的驗收條件去想,這條條件如果真的沒被滿足,會反映在哪個可以觀察到的數字或現象上。這樣選出來的指標,才會真正對應到當初在意的事,而不是隨便挑幾個看起來專業的數字擺著,卻跟真正該關心的問題沒什麼關係。指標選錯了,看起來很認真在監控,實際上等於什麼都沒看到。
這件事也提醒我,指標的數量從來就不是越多越好,指標一多,真正重要的訊號反而容易被淹沒在一堆次要的數字裡,每天要花時間掃過一堆看起來都差不多的數字,時間久了,注意力自然而然就會被稀釋掉,變得沒有那麼敏銳。我自己現在會逼自己篩選出真正關鍵的幾個指標,寧可少而精準,也不要多而模糊,這樣真正出現異常的那一刻,才容易第一時間就注意到,不會被淹沒在一堆無關緊要的雜訊裡,錯過了最該反應的時間點。
真實環境裡難免會遇到上線之後才發現的問題,這種時候,第一件事不是急著在正式環境裡邊查邊修,是先確認有沒有辦法退回到上一個還算穩定的狀態,讓真正在使用的人不會繼續受到影響。有了退回去的空間,才能夠靜下心來查問題出在哪裡,不用在使用者持續受影響的壓力下,倉促做出可能讓情況更糟的修正。
這件事我自己認為值得在部署流程設計的時候就先想清楚,而不是等到真的出事了才臨時想辦法退回去。臨時想的退回方式,很容易漏掉一些細節,比如退回版本之後,資料的狀態跟舊版本對不對得上,這種細節如果沒有事先想過,退回這個動作本身也可能製造新的問題,變成用一個問題去解決另一個問題。
我自己會刻意在正式推上去之前,先假裝這次上線出了問題,走一遍退回的流程,確認真的能順利退回,而不是等到真正出事的當下才第一次嘗試退回這個動作。這種預演聽起來多一道手續,實際上是把「退回機制到底能不能用」這件事,從一個沒驗證過的假設,變成一個真的操作過、確認可行的事實,兩者在真正出事的時候,可靠程度完全不一樣。
沒有預演過的退回機制,最容易在真正需要它的那一刻失靈,因為那個時刻通常伴隨著壓力跟時間緊迫,情緒一緊張,判斷力也會跟著打折,任何一個沒想清楚的細節都會被放大成更嚴重的麻煩。先在沒有壓力的時候把這條路走過一次,等真的需要用到的時候,走的是一條已知可行的路,而不是一條只在理論上應該可行、但從沒真正測過的路,這個差別在情況緊急的時候,會決定退回這個動作到底是真的解決了問題,還是不小心製造了更多新的問題。
部署跟監控這件事拆開來看,也有機械性跟判斷性的分別。照著固定流程執行部署步驟、定期抓取幾個關鍵指標的數值,這種偏機械性的工作,用中等等級的資源處理就好,重點是穩定執行,不需要特別聰明。真正需要判斷力的,是看到指標出現異常的時候,判斷這個異常嚴不嚴重、要不要退回版本、還是可以先觀察,這種需要拿捏輕重的決定,我會留給比較強的資源,或者乾脆自己直接判斷,不假手他人。
這個分法套用下來,日常大部分時間流程都是穩定跑著,只有真正出現異常的時候,才需要動用比較貴的判斷力,這樣整體運作起來,成本跟品質才能兼顧,不會為了要盯緊每個細節,把所有資源都耗在不需要判斷的機械性工作上。
這種分工也讓我自己不用一直盯著每一個指標的每一次波動,機械性的部分會固定去抓數據、固定去跑檢查,真正需要我自己介入的,只有那些被判斷成值得注意的異常,這樣一來,我可以把注意力留給真正重要的時刻,而不是被大量正常範圍內的日常波動分散掉,那種持續緊盯的狀態,長期下來反而會讓人對真正的異常變得遲鈍,把警覺心消耗在不需要警覺的地方。
真的發生問題、也處理完之後,我會找一個沒有參與處理過程的角色,回頭看整件事的來龍去脈,問題怎麼發生的、多久才被發現、處理的過程有沒有可以做得更好的地方。這件事跟前面講過的道理一樣,處理問題的人自己回頭看,很容易帶著處理當下的情緒跟立場,看不出真正該檢討的地方,換一個角色從頭看一次,才容易看出流程本身有沒有需要補強的漏洞。
處理問題當下的情緒,特別容易影響事後的判斷,緊張、想趕快解決的心情,會讓人在事後回顧的時候,傾向講一個讓自己覺得處理得還不錯的版本,這不是刻意隱瞞什麼,是情緒本身就會不自覺地扭曲回憶的角度。換一個沒有經歷過那種緊張情緒的角色來看,才能比較客觀地評估,這次的處理速度跟方式,跟真正該有的標準比起來,到底差在哪裡,而不是被當下那種鬆一口氣的心情牽著走,草草帶過真正該檢討的細節。
這種檢討做完之後,我會把發現的問題回頭補進部署流程或者監控的設定裡,不會讓同樣的問題有機會用同樣的方式再發生一次。這件事讓整套流程隨著每一次真實發生的狀況,慢慢變得更紮實,不是每次出事都從頭學一次同樣的教訓。
檢討的時候,我也會特別留意問題從發生到被發現之間,隔了多久。這段時間差如果很長,代表監控這一關本身就有漏洞,值得檢討的不只是問題本身怎麼發生的,還有為什麼花了這麼久才被注意到。有時候問題不難處理,真正的教訓是監控指標選錯了、看的頻率不夠,這種發現往往比問題本身更值得認真補強,因為它會影響到未來所有類似問題被發現的速度。
這段時間差本身,也可以當成一個持續追蹤的指標。每次真的出問題之後,記下這次從發生到發現花了多久,拉長時間來看,這個數字應該要越來越短,代表監控機制真的在進步。如果這個數字一直沒有改善,甚至變長,代表監控這件事雖然做了,卻沒有真正因為過去的經驗變得更靈敏,這種情況值得回頭認真檢視整套監控機制是不是哪裡出了根本性的問題。
跟前面講過的道理一樣,一個影響範圍很小、出錯了也很好收拾的小改動,不需要每次都搬出退回機制設計、監控指標核對這一整套。真正值得認真準備的,是那種影響範圍大、一旦出問題會讓不少人受到影響的改動,這種改動才值得花時間把上線前的環境差異、部署流程、監控退回機制都準備得比較完整。
這個判準套用久了,我發現自己在準備上線的時候,第一件事已經不是想著要準備哪些東西,是先花一點時間判斷這次改動的份量,再決定準備的規格要拉到多高。這種先分類再決定投入程度的習慣,本身就是我一路在用的判斷方式的一種具體實踐,遇到事情不會照著同一套標準做,而是先想清楚這件事的份量,再決定要用哪一套規格去應對。
判準還是同一個,這次上線如果出問題,會有多少人受到影響,多久才會被發現,退回去要花多少代價。影響小、退回容易的東西,正常跑一次部署流程就好。影響大、退回麻煩的東西,才值得花額外的時間把每個環節都準備周全。
遇到自己也抓不準的時候,我會偏向把這次上線當成影響比較大的情況來準備,這個習慣要付出的代價,是偶爾會多花一點時間去準備一次其實沒那麼必要的檢查,但比起輕估了影響範圍、結果真的出事了又完全沒有退回準備,這個代價其實還是划算得多。這條偏保守的判準,只要牽涉到會被真實使用者直接看到的東西,我都會抓得比其他階段更緊一些,畢竟前面幾個階段出了錯,代價通常都只有自己一個人承擔,上線之後才出錯,代價卻會直接轉嫁到正在使用的人身上。
老實講清楚這套做法的界限。部署流程穩定、監控指標齊全,能防的是已經想過會發生的那些狀況,防不住的是完全沒預料到的新情況。真實環境本來就比開發環境複雜得多,再怎麼仔細想過可能的差異,還是有可能遇到當初完全沒想到的狀況,這種時候,靠的不是流程本身,是遇到問題當下能不能冷靜判斷,先確保能退回去,再慢慢查清楚到底發生了什麼事。
這也是為什麼退回機制比任何一種預防措施都更值得優先準備,預防措施再多,也不可能涵蓋所有沒想過的情況,但只要退回機制真的可靠,遇到任何沒想過的狀況,都有一條路可以先讓真正在使用的人不受影響,再慢慢查清楚問題出在哪裡。把資源優先花在退回機制上,而不是想著把每一種可能的狀況都預先想到,是我自己覺得比較務實的做法,畢竟想不到的狀況,永遠比想得到的多,這件事沒有任何方法可以徹底解決,只能想辦法降低它造成的傷害。
還有一點要老實說,監控指標能告訴我東西是不是不正常,沒辦法直接告訴我為什麼不正常。指標異常只是一個訊號,代表這裡值得深入去看,實際的原因還是得靠人真正去查,這件事沒有捷徑,監控做得再完整,也只是縮短發現問題的時間,不能取代找出問題根源這個步驟。
自動化的部署流程也是類似的道理,它能確保每次執行的步驟都一模一樣,防的是人為疏漏造成的不一致,防不住的是流程本身設計得就有問題。如果整套流程從一開始的設計就有漏洞,自動化只會讓這個有漏洞的流程被更精準、更一致地重複執行,錯誤不會因為自動化就自動消失,反而會被複製得更徹底、更難察覺。這也是為什麼流程本身也需要偶爾回頭檢視,不能裝好就當作永遠不用再看,一套流程裝好之後最容易被遺忘,也最容易在被遺忘的狀態下持續累積問題。
從一句聽起來還不錯的需求開始,被逼問出裡面藏著的分岔,收斂成畫面跟架構,組隊分析出站得住腳的技術方向,落地成真正能跑的程式,核對過沒有悄悄跑偏,最後推上線,還要進一步確認它在真實世界裡真的能活得下去,不是上線那一刻就宣告勝利。這一整條路走完,回頭看最深的體會是,每一段都不是各自獨立的技能,是同一套判斷方式,換了場景重新套用一次。
一路走到這一步,最直觀的感覺是,工程師這個角色實際上要顧的事,遠比單純把程式寫出來要廣得多。以前分工清楚的時候,每個角色只要顧好自己負責的那一段就好,現在卻要自己判斷什麼時候該逼問、什麼時候該核對、什麼時候該退回去,這些原本分散在不同職位上的判斷力,全部都要收攏在同一個人身上。這並不是工程師這個職位突然變重了,是原本隱形分攤在一群人身上的判斷責任,現在全部集中顯現在一個人身上,變得清清楚楚看得見了,以前可以靠別人分擔的部分,現在無處可躲。
走過這整趟路,對我自己最大的改變,不是學會了多少新工具,而是對「完成」這兩個字的理解徹底變了。以前覺得程式寫完、能順利跑起來,就算是完成了,現在覺得真正的完成,是一整條鏈路從頭到尾都經得起檢驗,從需求對不對、設計收斂了沒、架構站不站得住、實作有沒有悄悄跑偏,一直到上線之後真的能穩穩活下去,任何一段沒有真的經過驗證,都不能算數。這個更嚴格的完成定義,是走完這整條路之後,最實質、也最不會忘記的收穫,比學會任何一個具體工具都更持久。
先確保手上的資訊是乾淨、值得信任的,再讓一個夠格的立場做出真正的取捨,最後留一道自己核對的手續,這套固定的動作在需求、設計、架構、實作、上線,每一段都反覆出現過,只是每次換了不同的名字、用了不同的工具去實現。一個人要撐起原本需要好幾個角色分工才走得完的路,靠的從來不是自己變得無所不能,是靠把這套動作紮紮實實地在每一段都認真做過一次,沒有哪一段可以理所當然地被跳過,也沒有任何一步真的是捷徑,走過的每一步都算數。
回頭把整條路線再走一遍,最現實的感受是,越到後面的階段,要付出的代價越具體、越沒有轉圜的餘地,需求談錯了頂多多花幾天重新逼問一輪,實作跑偏了重寫一段邏輯就好,等到了上線這一段,任何一個沒想清楚的地方,付出代價的已經不再只是自己一個人的時間,而是所有正在使用這個東西的人。這個代價結構上的根本變化,也是為什麼上線這段的核對跟準備,值得比前面任何一段都更謹慎小心一些,不是因為這段在技術上比較困難,是因為這段一旦出錯的後果,已經不再只是自己一個人默默承擔就好。
這件事也讓我重新想過一遍,前面每一段留下的紀錄跟文件,到了上線這一段,其實都變成排查問題時最珍貴的資產,這是我一開始留這些紀錄的時候完全沒料到的額外收穫。真的出狀況的時候,能不能快速回頭查到當初規劃講了什麼、架構為什麼要這樣設計,往往直接決定了整個排查問題的過程要花多久時間。前面每一段認真留下的紀錄,從來就不只是為了核對用的,到了真正出事的那一刻,這些紀錄會直接變成救命的資訊來源,這件事再次證明,當初花的那些看似多餘的力氣,沒有一分是真的白費的。